/* utility.c - utility functions for pam_grub
 *
 * Copyright (C) 2006 John N. Laliberte
 *
 * This file is part of pam_grub.
 *
 * pam_grub is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * pam_grub is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with pam_grub; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <glib-2.0/glib.h>
#include <glib-2.0/glib/gprintf.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "md5.h"

#include "utility.h"

void
generate_salt(char *crypted)
{
	const gchar *const seedchars =
			"./0123456789ABCDEFGHIJKLMNOPQRST"
			"UVWXYZabcdefghijklmnopqrstuvwxyz";

	//strcopy(crypted, "$1$");
	crypted[0] = '$';
	crypted[1] = '1';
	crypted[2] = '$';

	GTimeVal time;
	g_get_current_time(&time);

#ifdef PGDEBUG
	g_printf("generated time %d\n", time);
#endif

	GRand *seed = g_rand_new_with_seed(time.tv_usec);

	int i;
	for(i = 0; i < 8; i++)
	{
		crypted[3 + i] = seedchars[g_rand_int_range(seed, 0, 64)];
	}
	crypted[3 + i] = '$';

#ifdef PGDEBUG
	g_printf("seed: %s\n", crypted);
#endif
}

extern int
calculate_md5(char *clear_password, char *crypted_pass)
{
	// use grubs actual implementation so we don't ever
	// mess it up.
	generate_salt(crypted_pass);

#ifdef PGDEBUG
	g_printf("final salt: %s\n", crypted_pass);
#endif

	// call the grub md5_password routine
	int ret_val =  md5_password(clear_password, crypted_pass, 0);

#ifdef PGDEBUG
	g_printf("calculate_md5(crypted): %s\n", crypted_pass);
#endif

	return ret_val;
}

extern int
change_file_permissions_to_owner(gchar *filename)
{
	// root can read or write the file.
	// 0 success, -1 failure
	return chmod(filename, S_IRUSR|S_IWUSR);
}

extern int
change_file_owner_to_root(gchar *filename)
{
	// you should be root when running this program
	// we use effective uid / gid b/c they could be using sudo
	// 0 success, -1 failure.
	return chown(filename, geteuid(), getegid());
}

/* This method finds a line starting with a specific string such as
 * "password" up to the first space.
 * Returns -1 if it could not find a matching line in the file.
 * Otherwise it returns the line number.
 */
extern int
find_matching_line_in_file(gchar *filename, gchar *search_string)
{
        FILE *fp;

        // rt = reading text
        fp = fopen(filename, "rt");

        char line [80];
        char after_read [80];
        int i=0;
        while (fgets(line, 80, fp) != NULL)
        {
                // gets up to the first space
                // we are looking for the string password.
		// this could be much more robust.
                sscanf(line, "%s", after_read);

                if (0 == strcmp(search_string, after_read))
                        return i;

                i++;
        }

        fclose(fp);

        return -1;
}

extern int
backup_file(gchar *filename, gchar **file_contents)
{
        gchar *backup_name = g_strconcat(filename, ".bak", NULL);

        FILE *backup;
        backup = fopen(backup_name, "w");

        GError *error = NULL;
	// function expects a pointer to a pointer ( address )
	// thats why we have &pointer value.
        if (FALSE == g_file_get_contents(filename, file_contents, NULL, &error))
        {
                g_printerr("%s\n", error->message);
                g_error_free(error);

		return 1;
        }

	// might want to check to make sure all characters were written
        g_fprintf(backup, "%s", *file_contents);

        fclose(backup);
        g_free(backup_name);

	// passed all tests
	return 0;
}

extern int
replace_line(gchar *filename, gchar **contents, gchar *newline, int line_number)
{
	int ret_val = 1;

        // split string into lines
        gchar ** lines;
        lines = g_strsplit(*contents, "\n", 0);

	int i;
        for(i=0; lines[i] && strcmp("", lines[i]); i++)
        {
                if (i == line_number)
                {
#ifdef PGDEBUG
                        g_printf("line: %s\n", lines[i]);
			g_printf("replace with: %s\n", newline);
#endif
			lines[i] = newline;
			newline = NULL;
			ret_val = 0;
                }
        }

	// rejoin
	*contents = g_strjoinv("\n", lines);

	g_strfreev(lines);

	return ret_val;
}

extern int
write_file(gchar *filename, gchar *contents)
{
	// rewrite the file with the modified line.
	FILE *real_file;
	// overwrite the existing one
	real_file = fopen(filename, "w");

#ifdef PGDEBUG
	g_printf("contents before writing: %s\n", contents);
#endif

	g_fprintf(real_file, "%s", contents);

	fclose(real_file);

	return 0;
}
